package libnetdb.dm.assoc.util;

import java.util.*;

/**
 * A <code>ControllableTask</code> is a new type of <code>
 * Task</code> that it is possible be commanded to cancel,
 * pause, resume the process of the task. It also provides an
 * <code>ProgressListener</code> interface for others to moniter
 * the progress of this task.
 *
 * @author Cheng-Ru Lin
 * @version 1.0 2002/8
 */
abstract public class ControllableTask extends Task {

    final static private Timer timer = new Timer(true);
    final static private int PROGRESS_UPDATED = 0x01;
    final static private int STAGEPROGRESS_UPDATED = 0x02;

    private ArrayList listeners = new ArrayList();
    private String stage = "";
    private float progress = -1;
    private float stage_progress = -1;
    private boolean is_paused = false;
    private boolean is_canceled = false;
    private int change_state = 0x00;
    private long update_interval = 250;
    private ProgressUpdater updater = null;

    /**
     * A <code>ControllableTask</code> can pass this <code>controllee
     * </code> to subtasks in order to controll the progress of the
     * subtask and get progress reports back.
     */
    final protected Controllee controllee = new Controllee(this);

    private void fireProgressChanged(){
        change_state &= ~PROGRESS_UPDATED;
        Iterator it = listeners.iterator();
        while(it.hasNext()){
            ProgressListener listener = (ProgressListener) it.next();
            listener.progressChanged(this, progress);
        }
    }

    private void fireStageProgressChanged(){
        change_state &= ~STAGEPROGRESS_UPDATED;
        Iterator it = listeners.iterator();
        while(it.hasNext()){
            ProgressListener listener = (ProgressListener) it.next();
            listener.stageProgressChanged(this, stage_progress);
        }
    }

    private void fireTaskPaused(){
        Iterator it = listeners.iterator();
        while(it.hasNext()){
            ProgressListener listener = (ProgressListener) it.next();
            listener.taskPaused(this);
        }
    }

    private void fireTaskResumed(){
        Iterator it = listeners.iterator();
        while(it.hasNext()){
            ProgressListener listener = (ProgressListener) it.next();
            listener.taskResumed(this);
        }
    }

    private synchronized void runUpdater(){
        if(updater != null) updater.run();
    }

    /**
     * Adds the specified <code>ProgressListener</code>.
     *
     * @param listener the listener to be added
     */
    public void addProgressListener(ProgressListener listener){
        listeners.add(listener);
    }

    /**
     * Removes the specified <code>ProgressListener</code>.
     *
     * @param listener the listener to be added
     */
    public void removeProgressListener(ProgressListener listener){
        listeners.remove(listener);
    }

    /**
     * Gets the current stage of the controlled task. The stage is a string
     * described the current subtask running in the controlled task.
     * @return The current stage of the controlled task
     */
    public String getStage(){
        return stage;
    }

    /**
     * Gets the corrent progress of the controlled task.
     * The progress is a float number between 0 and 1 indicating
     * the percentage of completeness of the controlled task.
     * A negative returned value indicates the current progress
     * cannot be determined.
     *
     * @return the progress of the controlled task
     */
    public float getProgress(){
        return progress;
    }

    /**
     * Gets the corrent progress of the stage. The progress is a
     * float number between 0 and 1 indicating the percentage of
     * completeness of the current stage of the task. A negative
     * returned value indicates the current progress cannot be
     * determined.
     *
     * @return the progress of the current stage of the controlled task
     */
    public float getStageProgress(){
        return stage_progress;
    }

    /**
     * Cancels the controlled task. Calling this function will
     * cause the current state changing from RUNNING to CANCELING.
     * Note that this only an suggestion to the controlled task,
     * which means that the controlled task may not stop immediately.
     */
    public void cancel(){
        this.is_canceled = true;
        this.resume();
    }

    /**
     * Pauses the controlled task so that it will release the
     * CPU resources. Note that it is only a suggestion to the
     * controllable task. This command will be valid only if
     * the current state is RUNNING.
     */
    public void pause(){
        this.is_paused = true;
    }

    /**
     * Resumes the puased task.
     *
     * @see #pause()
     */
    public synchronized void resume(){
        this.is_paused = false;
        this.notify();
    }

    /**
     * Tests if the <code>Controller.pause()</code> has been executed.
     *
     * @return <code>true</code> if a pause command has been made;
     *         <code>false</code> otherwise.
     * @see Controller#pauseTask()
     */
    public boolean isPaused(){
        return is_paused;
    }

    /**
     * Processes the user commands, such as <code>pause()</code>
     * and <code>cancel()</code>. If <code>cancel()</code> has been
     * executed, when calls this function, a <code>
     * UserCancelException</code> will be thrown. This exception
     * should not be caught inside the task. On the other hand, if
     * <code>Controller.pause()</code> has been executed, when
     * calling this function, the current thread will be blocked
     * until <code>Controller.resume()</code> has been called.
     *
     * @throws UserCancelException if one has executed <code>cancel()</code>
     *         command.
     * @throws InterruptedException if current thread has been interrupted by other thread.
     *         The <i>interrupted status</i> of the current thread is cleared when
     *         this exception is thrown.
     *
     * @see #cancel()
     * @see #pause()
     * @see #resume()
     */
    final protected synchronized void processCommands()
            throws UserCancelException, InterruptedException{
        if(is_paused){
            fireTaskPaused();
            while(is_paused) this.wait();
            fireTaskResumed();
        }
        if(is_canceled) throw new UserCancelException();
    }

    /**
     * Sets the current stage of the controlled task.
     *
     * @param stage the current stage
     */
    protected void setStage(String stage){
        if (!this.stage.equals(stage)){
            runUpdater();
            this.stage = stage;
            this.stage_progress = -1.0f;
            Iterator it = listeners.iterator();
            while (it.hasNext()){
                ProgressListener listener = (ProgressListener)it.next();
                listener.stageChanged(this, stage);
            }
        }
    }

    protected void setState(int state){
        runUpdater();
        super.setState(state);
    }

    protected void postMessage(String message){
        runUpdater();
        Iterator it = listeners.iterator();
        while(it.hasNext()){
            ProgressListener listener = (ProgressListener)it.next();
            listener.messagePosted(this, message);
        }
    }

    /**
     * Sets the current progress of the whole task. The progress is a
     * <code>float</code> number in the range of [0,1] indicating the
     * completeness of the task.
     *
     * @param progress the progress of the task
     */
    protected void setProgress(float progress){
        this.progress = progress;
        change_state |= PROGRESS_UPDATED;
        if(update_interval > 0){
            synchronized(this){
                if(updater == null){
                    updater = new ProgressUpdater();
                    timer.schedule(updater, update_interval);
                }
            }
        }else fireProgressChanged();
    }

    /**
     * Sets the progress of the current stage. The progress is a
     * <code>float</code> number in the range of [0,1] indicating
     * the completeness of the stage.
     *
     * @param progress The working progress just now.
     */
    protected void setStageProgress(float progress){
        this.stage_progress = progress;
        change_state |= STAGEPROGRESS_UPDATED;
        if(update_interval > 0){
            synchronized(this){
                if(updater == null){
                    updater = new ProgressUpdater();
                    timer.schedule(updater, update_interval);
                }
            }
        }else fireStageProgressChanged();
    }

    /**
     * Sets the update interval of this task. The update interval
     * is the delay time (in milliseconds) that an stage progress
     * or progress update will be informed the registered <code>
     * ProgressListener</code>. Futher updated of progress of
     * stage progress will replace this updated event, i.e., only
     * the latest updated will be sent to the listeners.
     *
     * @param interval the update interval in milliseconds
     */
    public void setUpdateInterval(long interval){
        this.update_interval = interval;
    }

    /**
     * Gets the update interval.
     *
     * @return the update interval (in milliseconds)
     */
    public long getUpdateInterval(){
        return update_interval;
    }

    private class ProgressUpdater extends TimerTask{
        public void run(){
            synchronized(ControllableTask.this){
                super.cancel();
                updater = null;
            }
            if((change_state & PROGRESS_UPDATED)!=0)
                fireProgressChanged();
            if((change_state & STAGEPROGRESS_UPDATED)!=0)
                fireStageProgressChanged();
        }
    }
}
